{
  // 有多个请求接口，如何做到只能上限三个异步请求接口，只有等其中一个或多个结束后，
  // 在加入新的后续接口进来，来现实同时请求异步上限数量的问题。
  // 队列的实现
  // 实现队列思路：维护一个num来记录当前的请求数，如果已经到三个了，剩下的就放进队列里，
  // 然后现在正在请求的，有完成的，就从队列里拿出一个来去请求。

  // 所有请求数组
  let urls = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
  ];
  let limit = 4; // 请求数量上限
  let onLimit = 0; // 当前请求数量
  queue();

  // 轮询
  function queue() {
    let timer = setInterval(() => {
      // 当前请求数量小于请求上限的时候请求数据
      if (limit > onLimit) {
        fetchData(urls);
      }
      if (urls.length === 0) clearInterval(timer);
    }, 100);
  }

  function fetchData(urls) {
    return new Promise((resolve) => {
      const url = urls.shift();
      onLimit++;
      if (url) {
        (async () => {
          const res = await fetch(
            `https://www.fastmock.site/mock/f29153ea802b2006237d862f7a54b82b/base/test?version=${url}`
          ).then((data) => {
            return data.json();
          });
          console.log(`当前发送：${res.data.version}`);
          resolve(url);
        })();
      }
    }).finally(() => {
      // 执行完一个请求，从队列里拉出来一个，继续请求
      if (urls.length > 0) {
        onLimit--;
      }
    });
  }
}

{
  // 有多个请求接口，如何做到只能上限三个异步请求接口，只有等其中一个或多个结束后，
  // 在加入新的后续接口进来，来现实同时请求异步上限数量的问题。
  // 队列的实现
  // 实现队列思路：记录当前请求上限数量，遍历到当前上限fn，抛进promise.all中，
  // 而每个fn执行完毕后，判断请求队列中是否还有，有的话在继续执行当前fn函数，

  // 请求urls数组
  let urls = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
  ];
  // 请求上限
  const limit = 5;

  function fetchData(urls, limit, callback) {
    // 具体的每一个fetch请求
    function _send(urls) {
      return new Promise((resolve) => {
        const url = urls.shift();
        if (url) {
          setTimeout(() => {
            // fetch data
            console.log(`当前发送url：${url}`);
            // 将结果resolve出去
            resolve(url);
          }, 1000);
        }
      }).finally(() => {
        // 可以理解递归，finally 方法是不管 promise 执行结果如何都会执行
        // 每一个异步执行完毕，就递归执行下一个（从数组中拿，直到数组为空）
        if (urls.length > 0) {
          return _send(urls);
        }
      });
    }

    let asyncList = [];
    // 遍历请求上限，将每一个fetch请求 _send(urls) 推入执行数组 asyncList
    while (limit--) {
      asyncList.push(_send(urls));
    }

    // 所有的异步执行完，一次性执行
    return Promise.all(asyncList).then(callback);
  }

  fetchData(urls, limit, (list) => console.log(`all urls sended`, list));
}

{
  // 前端并发10个相同的请求，怎么控制为只发一个请求？
  //
  // 同时发多个相同的请求，如果第一个请求成功，那么剩余的请求都不会发出，成功的结果作为剩余请求返回
  // 如果第一个请求失败了，那么接着发编号为2的请求，如果请求成功，那么剩余的请求都不会发出，成功的结果作为剩余请求返回
  // 如果第二个请求失败了，那么接着发编号为3的请求，如果请求成功，那么剩余的请求都不会发出，成功的结果作为剩余请求返回
  // ...以此递推，直到遇到最坏的情况需要发送最后一个请求
  //
  // 为什么要缓存接口，举个场景。输入关键字搜索，监听的是input事件，在你增删关键字的时候，就会出现请求参数一样的场景，
  // 这时候就符合防抖+前端接口缓存的方式。遇到相同关键字直接拉之前的缓存。

  // async function fetchData(a) {
  //   const data = await fetch('//127.0.0.1:3000/test');
  //   const d = await data.json();
  //   console.log(d);
  //   return d;
  // }
  // fetchData(2); // 编号 1
  // fetchData(2); // 编号 2
  // fetchData(2); // 编号 3
  // fetchData(2); // 编号 4
  // fetchData(2); // 编号 5
  // fetchData(2); // 编号 6

  // 法一
  // cacheAsync只会缓存成功的请求，如果失败了，直接拉起新的请求，
  // 但是如果是并发场景，相同的请求是无法命中缓存的，会出现连续发送三个请求的问题，所以无法处理并发的场景
  const cacheAsync = function (fn) {
    const cache = Object.create(null);

    return async (str) => {
      const hit = cache[str];

      if (hit) return hit;

      // 只缓存成功promise，失败的直接重新请求
      return (cache[str] = await fn(str));
    };
  };
  const fetch2 = cacheAsync(fetchData);
  fetch2(2);
  fetch2(2);
  fetch2(2);

  // 法二 （缓存+并发控制）
  // 思路：
  // 1 每个请求都返回一个新的Promise，Promise的exector的执行时机，通过一个队列保存；
  // 2 当队列长度为1的时候，执行一次请求，如果成功，那么遍历队列中的exector，拿到请求结果然后resolve；
  // 3 如果请求失败了，那么吧这个Promise reject掉，同时出栈。然后递归调用next；
  // 4 知道exector队列清空为止；
  const cacheAsync = (promiseGenerator, symbol) => {
    const cache = new Map();
    const never = Symbol();
    return async (params) => {
      return new Promise((resolve, reject) => {
        // 可以提供键值
        symbol = symbol || params;
        let cacheCfg = cache.get(symbol);
        if (!cacheCfg) {
          cacheCfg = {
            hit: never,
            exector: [{ resolve, reject }],
          };
          cache.set(symbol, cacheCfg);
        } else {
          // 命中缓存
          if (cacheCfg.hit !== never) {
            return resolve(cacheCfg.hit);
          }
          cacheCfg.exector.push({ resolve, reject });
        }

        const { exector } = cacheCfg;

        // 处理并发，在请求还处于pending过程中就发起了相同的请求
        // 拿第一个请求
        if (exector.length === 1) {
          const next = async () => {
            try {
              if (!exector.length) return;
              const response = await promiseGenerator(params);
              // 如果成功了，那么直接resolve掉剩余同样的请求
              while (exector.length) {
                // 清空
                exector.shift().resolve(response);
              }
              // 缓存结果
              cacheCfg.hit = response;
            } catch (error) {
              // 如果失败了 那么这个promise的则为reject
              const { reject } = exector.shift();
              reject(error);
              next(); // 失败重试，降级为串行
            }
          };
          next();
        }
      });
    };
  };

  var fetch2 = cacheAsync(fetchData, 'test2');

  async function fetchData(a) {
    const data = await fetch('//127.0.0.1:3000/test'); // server1.js
    const d = await data.json();
    console.log(d);
    return d;
  }
  // 并发6个相同的请求
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
  console.log(fetch2(2));
}
